#include "PLYModel.h"
#include "Utility.h"
using namespace std;

Model3D::Model3D()
{
	boundingBox = BoundingBox();
}

//int Model3D::NumPoints = 40000;
//int Model3D::NumVertices = 6000;

void Model3D::reset()
{
	memset(points,0,sizeof(points));
	memset(colors,0,sizeof(colors));
	memset(vertices,0,sizeof(vertices));
	boundingBox.reset();
}

void Model3D::init(string sfileName, string element)
{
	elementType = element;
	//read file & generate geometry
	if(element == "plane")
	{
		generatePlane();
		//generateCube();
	}
	else
	{
		readFile(sfileName);
	}

	//VAO VBO
	createVBO();

	//load Shaders
	initShader();

	// sets the default color to clear screen
    glClearColor( 1.0, 1.0, 1.0, 1.0 ); // white background
	glClearColor( 0.0, 0.0, 0.0, 0.0 ); // black background

	//projection
	setupProjection();
}

void Model3D::createVBO()
{
	//create VAO
	//GLuint vao;
    glGenVertexArrays( 1, &vao );
    glBindVertexArray( vao );

	//create VBO
	glGenBuffers( 1, &bufferId );
    glBindBuffer( GL_ARRAY_BUFFER, bufferId );
    glBufferData( GL_ARRAY_BUFFER, sizeof(points) + sizeof(colors),
		  NULL, GL_STATIC_DRAW );
    glBufferSubData( GL_ARRAY_BUFFER, 0, sizeof(points), points );
    glBufferSubData( GL_ARRAY_BUFFER, sizeof(points), sizeof(colors), colors );
}

void Model3D::initShader()
{
	//load shaders
	programId = InitShader( "vshader1.glsl", "fshader1.glsl" );
    glUseProgram( programId );

	glBindVertexArray( vao );
	glBindBuffer( GL_ARRAY_BUFFER, bufferId );

     // set up vertex arrays
    GLuint vPosition = glGetAttribLocation( programId, "vPosition" );
    glEnableVertexAttribArray( vPosition );
    glVertexAttribPointer( vPosition, 4, GL_FLOAT, GL_FALSE, 0,
			   BUFFER_OFFSET(0) );

    GLuint vColor = glGetAttribLocation( programId, "vColor" ); 
    glEnableVertexAttribArray( vColor );
    glVertexAttribPointer( vColor, 4, GL_FLOAT, GL_FALSE, 0,
			   BUFFER_OFFSET(sizeof(points)) );
}

void Model3D::triangle(int a, int b, int c)
{
	colors[Index] = color4( 0.0, 1.0, 0.0, 1.0 ); points[Index] = vertices[a]; Index++;
	colors[Index] = color4( 0.0, 1.0, 0.0, 1.0 ); points[Index] = vertices[b]; Index++;
	colors[Index] = color4( 0.0, 1.0, 0.0, 1.0 ); points[Index] = vertices[c]; Index++;
}

void Model3D::quad( int a, int b, int c, int d )
{
    colors[Index] = color4( 0.0, 1.0, 0.0, 1.0 ); points[Index] = vertices[a]; Index++;
    colors[Index] = color4( 0.0, 1.0, 0.0, 1.0 ); points[Index] = vertices[b]; Index++;
    colors[Index] = color4( 0.0, 1.0, 0.0, 1.0 ); points[Index] = vertices[c]; Index++;
    colors[Index] = color4( 0.0, 1.0, 0.0, 1.0 ); points[Index] = vertices[a]; Index++;
    colors[Index] = color4( 0.0, 1.0, 0.0, 1.0 ); points[Index] = vertices[c]; Index++;
    colors[Index] = color4( 0.0, 1.0, 0.0, 1.0 ); points[Index] = vertices[d]; Index++;
}

void Model3D::generateCube()
{
	//cube vertices
	vertices[0] = point4( -0.5, -0.5,  0.5, 1.0 );
    vertices[1] = point4( -0.5,  0.5,  0.5, 1.0 );
    vertices[2] = point4(  0.5,  0.5,  0.5, 1.0 );
    vertices[3] = point4(  0.5, -0.5,  0.5, 1.0 );
    vertices[4] = point4( -0.5, -0.5, -0.5, 1.0 );
    vertices[5] = point4( -0.5,  0.5, -0.5, 1.0 );
    vertices[6] = point4(  0.5,  0.5, -0.5, 1.0 );
    vertices[7] = point4(  0.5, -0.5, -0.5, 1.0 );

	//cube quads
	quad( 1, 0, 3, 2 );
    quad( 2, 3, 7, 6 );
    quad( 3, 0, 4, 7 );
    quad( 6, 5, 1, 2 );
    quad( 4, 5, 6, 7 );
    quad( 5, 4, 0, 1 );
}

void Model3D::generatePlane()
{

	const float height = 25.0f;
	const float width = 25.0f;
	const float attitude = 0.0f;
	
	vertices[0] = point4(-width,attitude,-height,1.0f);
	vertices[1] = point4(0.0f,attitude,-height,1.0f);
	vertices[2] = point4(width,attitude,-height,1.0f);

	vertices[3] = point4(-width,attitude,0.0f,1.0f);
	vertices[4] = point4(0.0f,attitude,0.0f,1.0f);
	vertices[5] = point4(width,attitude,0.0f,1.0f);

	vertices[6] = point4(-width,attitude,height,1.0f);
	vertices[7] = point4(0.0f,attitude,height,1.0f);
	vertices[8] = point4(width,attitude,height,1.0f);

	quad(0,3,4,1);
	quad(1,4,5,2);
	quad(3,6,7,4);
	quad(4,7,8,5);
	
	
}


void Model3D::readFile(string sfileName)
{
	//clear
	reset();

	//open file
	const char* fileName = sfileName.c_str();
	fstream inStream;
	inStream.open(fileName,ios::in); //open file
	if(inStream.fail())
	{
		cout<<"file open failure!"<<endl;
		return;
	}

	//read file
	string line;
	vector<string> splittedString;

	getline(inStream,line);//ply
		if(line!="Ply" && line!="ply")
		{
			return;//not present, exit
		}
		getline(inStream,line);//skip: format ascii
		getline(inStream,line);//read: element vertex #
		splittedString.clear();
		splittedString = Utility::split(line," ");
		numOfVertices = atoi(splittedString[2].c_str());

		for(int index = 0;index<3;index++)
		{
			getline(inStream,line);//skip: property*3
		}
		getline(inStream,line);//read: element face #
		splittedString.clear();
		splittedString = Utility::split(line," ");
		numOfPolygons = atoi(splittedString[2].c_str());

		getline(inStream,line);//skip: property
		getline(inStream,line);//skip: end_header

		//read vertices & calc bounding box
		for(int index = 0;index<numOfVertices;index++)
		{
			getline(inStream,line);
			splittedString.clear();
			splittedString = Utility::split(line," ");
			point4 vertex;
			vertex.x = atof(splittedString[0].c_str());
			vertex.y = atof(splittedString[1].c_str());
			vertex.z = atof(splittedString[2].c_str());
			vertex.w = 1.0f;
			vertices[index] = vertex;

			//update boundingBox
			boundingBox.update(vertex);
		}

		if(elementType == "sphere")
		{
			sphereUniform();
		}
		else if(elementType == "cylinder")
		{
			cylinderUniform();
		}

		//clear Index for points
		Index = 0;

		//read polygons
		for(int index1 = 0;index1<numOfPolygons;index1++)
		{
			getline(inStream,line);
			splittedString.clear();
			splittedString = Utility::split(line," ");
			GLint vIndex[3];
			for(int index2 = 0;index2<3;index2++){
				vIndex[index2] = atoi(splittedString[index2+1].c_str());
			}
			triangle(vIndex[0],vIndex[1],vIndex[2]);
		}

		inStream.close();

}

void Model3D::sphereUniform()
{
	//center at 0,0,0
	float diameter = 0.2;
	point4 centerOffset = boundingBox.getCenter();
	point3 range = boundingBox.getRange();

	for(int index = 0;index<numOfVertices;index++)
	{
		vertices[index] -= centerOffset;
		vertices[index].x *= diameter/range.x;
		vertices[index].y *= diameter/range.y;
		vertices[index].z *= diameter/range.z;
		vertices[index].w = 1.0f;
	}

	//change boundingBox
	boundingBox.setMax(point3(diameter/2,diameter/2,diameter/2));
	boundingBox.setMin(point3(-diameter/2,-diameter/2,-diameter/2));

}

void Model3D::cylinderUniform()
{
	float diameter = 0.2f;
	int time = 5;// height/diameter
	point4 centerOffset = boundingBox.getCenter();
	point3 range = boundingBox.getRange();
	//rotate
	for(int index = 0;index < numOfVertices;index++)
	{
		vertices[index] -= centerOffset;
		vertices[index].x *= diameter/range.x;
		vertices[index].y *= diameter/range.y;
		vertices[index].z *= diameter*time/range.z;
		vertices[index].z -= diameter*time/2;

		point4 vertex = vertices[index];
		vertices[index].z = vertex.y;
		vertices[index].y = -vertex.z;

		vertices[index].w = 1.0f;
	}

	//change boundingBox
	boundingBox.setMax(point3(diameter/2,diameter*time,diameter/2));
	boundingBox.setMin(point3(-diameter/2,0.0f,-diameter/2));
	
}

void Model3D::setupProjection()
{
	//width/height
	Angel::mat4 perspectiveMat = Angel::Perspective((GLfloat)100.0, (GLfloat)512/(GLfloat)512, (GLfloat)0.1, (GLfloat) 100.0);

	float viewMatrixf[16];
	viewMatrixf[0] = perspectiveMat[0][0];viewMatrixf[4] = perspectiveMat[0][1];
	viewMatrixf[1] = perspectiveMat[1][0];viewMatrixf[5] = perspectiveMat[1][1];
	viewMatrixf[2] = perspectiveMat[2][0];viewMatrixf[6] = perspectiveMat[2][1];
	viewMatrixf[3] = perspectiveMat[3][0];viewMatrixf[7] = perspectiveMat[3][1];

	viewMatrixf[8] = perspectiveMat[0][2];viewMatrixf[12] = perspectiveMat[0][3];
	viewMatrixf[9] = perspectiveMat[1][2];viewMatrixf[13] = perspectiveMat[1][3];
	viewMatrixf[10] = perspectiveMat[2][2];viewMatrixf[14] = perspectiveMat[2][3];
	viewMatrixf[11] = perspectiveMat[3][2];viewMatrixf[15] = perspectiveMat[3][3];

	GLuint viewMatrix = glGetUniformLocationARB(programId, "projection_matrix");
	glUniformMatrix4fv( viewMatrix, 1, GL_FALSE, viewMatrixf);
}

void Model3D::drawModel(mat4 transform, vec4 color)
{
	//display matrix setup
	_ctm = Angel::identity();
	_ctm = _ctm * transform ;

	
	
	//modelView
	Angel::mat4 modelMat = Angel::identity();
	modelMat = modelMat * _ctm ;
	float modelMatrixf[16];
	modelMatrixf[0] = modelMat[0][0];modelMatrixf[4] = modelMat[0][1];
	modelMatrixf[1] = modelMat[1][0];modelMatrixf[5] = modelMat[1][1];
	modelMatrixf[2] = modelMat[2][0];modelMatrixf[6] = modelMat[2][1];
	modelMatrixf[3] = modelMat[3][0];modelMatrixf[7] = modelMat[3][1];

	modelMatrixf[8] = modelMat[0][2];modelMatrixf[12] = modelMat[0][3];
	modelMatrixf[9] = modelMat[1][2];modelMatrixf[13] = modelMat[1][3];
	modelMatrixf[10] = modelMat[2][2];modelMatrixf[14] = modelMat[2][3];
	modelMatrixf[11] = modelMat[3][2];modelMatrixf[15] = modelMat[3][3];
	
	// set up projection matricies
	GLuint modelMatrix = glGetUniformLocationARB(programId, "model_matrix");
	glUniformMatrix4fv( modelMatrix, 1, GL_FALSE, modelMatrixf );
	

	//change color
	for(int index = 0;index<Index;index++)
	{
		colors[index] = color;
	}

	//bind again
	glBindVertexArray( vao );
	glBindBuffer( GL_ARRAY_BUFFER, bufferId );
	
	glBufferData( GL_ARRAY_BUFFER, sizeof(points) + sizeof(colors),
		  NULL, GL_STATIC_DRAW );
    glBufferSubData( GL_ARRAY_BUFFER, 0, sizeof(points), points );
    glBufferSubData( GL_ARRAY_BUFFER, sizeof(points), sizeof(colors), colors );

	//draw
	glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
	glEnable( GL_DEPTH_TEST );
	glDrawArrays( GL_TRIANGLES, 0, NumPoints );
	glDisable( GL_DEPTH_TEST ); 
	//glFlush();
}